/*==============================================================================================================
  
  [ cNetworkDrive - Map Network Drive API Class ]
  -----------------------------------------------
  Copyright (c)2006-2007 aejw.com
  http://www.aejw.com/
  
 Build:         0028 - March 2007
 Thanks To:     'jsantos98', 'FeLiZk' from CodeProject.com for there comments and help
                'MartinPreis' for reporting two bugs
 EULA:          Creative Commons - Attribution-ShareAlike 2.5
                http://creativecommons.org/licenses/by-sa/2.5/

==============================================================================================================*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace CommonUtils
{

	/// <summary>
	/// Network Drive Mapping class / wrapper
	/// </summary>
	/// <remarks>Maps, ummaps and general functions for network drives</remarks>
	public class NetworkDrive
	{

		#region Public variables and propertys
		public bool IsMapped { get; set; }
		private bool _SaveCredentials = false;
		/// <summary>
		/// Option to save credentials on reconnection...
		/// </summary>
		public bool SaveCredentials
		{
			get
			{
				return _SaveCredentials;
			}
			set
			{
				_SaveCredentials = value;
			}
		}

		private bool _Persistent = false;
		/// <summary>
		/// Option to reconnect drive after log off / reboot...
		/// </summary>
		public bool Persistent
		{
			get
			{
				return _Persistent;
			}
			set
			{
				_Persistent = value;
			}
		}

		private bool _Force = false;
		/// <summary>
		/// Option to force connection if drive is already mapped...
		/// or force disconnection if network path is not responding...
		/// </summary>
		public bool Force
		{
			get
			{
				return _Force;
			}
			set
			{
				_Force = value;
			}
		}

		private bool _PromptForCredentials = false;
		/// <summary>
		/// Option to prompt for user credintals when mapping a drive
		/// </summary>
		public bool PromptForCredentials
		{
			get
			{
				return _PromptForCredentials;
			}
			set
			{
				_PromptForCredentials = value;
			}
		}

		private bool _FindNextFreeDrive = false;
		/// <summary>
		/// Option to auto select the 'LocalDrive' property to next free driver letter when mapping a network drive
		/// </summary>
		public bool FindNextFreeDrive
		{
			get
			{
				return _FindNextFreeDrive;
			}
			set
			{
				_FindNextFreeDrive = value;
			}
		}

		private string _LocalDrive = null;
		/// <summary>
		/// Drive to be used in mapping / unmapping (eg. 's:')
		/// </summary>
		public string LocalDrive
		{
			get
			{
				return _LocalDrive;
			}
			set
			{
				if (value == null || value.Length == 0)
				{
					_LocalDrive = null;
				}
				else
				{

					_LocalDrive = value.Substring(0, 1) + ":";
				}
			}
		}

		private string _ShareName = "";
		/// <summary>
		/// Share address to map drive to. (eg. '\\Computer\C$')
		/// </summary>
		public string ShareName
		{
			get
			{
				return _ShareName;
			}
			set
			{
				_ShareName = value;
			}
		}

		public string Drive { get { return (Environment.UserInteractive) ? ShareName : LocalDrive; } }

		/// <summary>
		/// Returns a string array of currently mapped network drives (both available and unavailable).
		/// </summary>
		public Dictionary<string, string> MappedDrives
		{
			get
			{
				Dictionary<string, string> driveArray = new Dictionary<string, string>();
                Dictionary<string, NetResource> driveDictionary = WNetResource();
                foreach (string driveLetter in System.IO.Directory.GetLogicalDrives())
                {
                    if (PathIsNetworkPath(driveLetter))
                    {
                        driveArray.Add(driveLetter, GetMappedShareName(driveLetter));
                    }
                }

				foreach (KeyValuePair<string, NetResource> netDrive in driveDictionary)
				{
					if (PathIsNetworkPath(netDrive.Key))
                    {
                        if (!driveArray.ContainsKey(netDrive.Key))
                        {
                            driveArray.Add(netDrive.Key, netDrive.Value.lpRemoteName);
                        }
					}
				}
				return driveArray;
			}
		}

        /// <summary>
        /// Returns a string array of all drives associated with a machine. (Both logical and network drives)
        /// </summary>
        public string[] AllDrives
        {
            get
            {
                List<string> driveArray = new List<string>();
                Dictionary<string, NetResource> netDrives = WNetResource();
				string[] logicalDrives = Directory.GetLogicalDrives();
                driveArray.AddRange(logicalDrives);
                foreach (KeyValuePair<string, NetResource> kvp in netDrives)
                {
                    driveArray.Add(kvp.Key);
                }
                driveArray.Sort();

                return driveArray.ToArray();
            }
        }
		#endregion

		#region Public functions

		/// <summary>
		/// Method to determine if a drive is available and connected
		/// </summary>
		/// <returns>Returns a boolean value denoting whether the class-associated drive is connected or not</returns>
		public bool IsConnected()
		{
			return IsConnected(_LocalDrive, _ShareName);
		}

		/// <summary>
		/// Method to determine if a drive is available and connected
		/// </summary>
		/// <param name="inLocalName">The drive letter to check (E.g. 'C:\')</param>
		/// <param name="inShareName">The network sahre location associated with the drive letter (E.g. '\\Computer\share')</param>
		/// <returns>Returns a boolean value denoting whether the passed-in drive is mapped and available</returns>
		public bool IsConnected(string inLocalName, string inShareName)
		{
			return System.IO.Directory.GetLogicalDrives().Contains(inLocalName.Substring(0, 1) + ":\\");
		}

		/// <summary>
		/// Map network drive
		/// </summary>
		public void MapDrive()
		{
			DoMapDrive(null, null);
		}

		/// <summary>
		/// Map network drive (using supplied Username and Password)
		/// </summary>
		/// <param name="username">Username passed for permissions / credintals ('Username' may be passed as null, to map using only a password)</param>
		/// <param name="password">Password passed for permissions / credintals</param>
		public void MapDrive(string username, string password)
		{
			DoMapDrive(username, password);
		}

		/// <summary>
		/// Set common propertys, then map the network drive
		/// </summary>
		/// <param name="localDrive">LocalDrive to use for connection</param>
		/// <param name="shareName">Share name for the connection (eg. '\\Computer\Share')</param>
		/// <param name="force">Option to force dis/connection</param>
		public void MapDrive(string localDrive, string shareName, bool force)
		{
			_LocalDrive = localDrive;
			_ShareName = shareName;
			_Force = force;
			DoMapDrive(null, null);
		}

		/// <summary>
		/// Set common propertys, then map the network drive
		/// </summary>
		/// <param name="localDrive">Password passed for permissions / credintals</param>
		/// <param name="force">Option to force dis/connection</param>
		public void MapDrive(string localDrive, bool force)
		{
			_LocalDrive = localDrive;
			_Force = force;
			DoMapDrive(null, null);
		}

		/// <summary>
		/// Unmap network drive
		/// </summary>
		public void UnMapDrive()
		{
			DoUnMapDrive();
		}

		/// <summary>
		/// Unmap network drive
		/// </summary>
		public void UnMapDrive(string localDrive)
		{
			_LocalDrive = localDrive;
			DoUnMapDrive();
		}

		/// <summary>
		/// Unmap network drive
		/// </summary>
		public void UnMapDrive(string localDrive, bool force)
		{
			_LocalDrive = localDrive;
			_Force = force;
			DoUnMapDrive();
		}

		/// <summary>
		/// Check / restore persistent network drive
		/// </summary>
		public void RestoreDrives()
		{
			DoRestoreDrive(null);
		}

		/// <summary>
		/// Check / restore persistent network drive
		/// </summary>
		public void RestoreDrive(string localDrive)
		{
			DoRestoreDrive(localDrive);
		}

		/// <summary>
		/// Display windows dialog for mapping a network drive (using Desktop as parent form)
		/// </summary>		
		public void ShowConnectDialog()
		{
			DoDisplayDialog(System.IntPtr.Zero, 1);
		}

		/// <summary>
		/// Display windows dialog for mapping a network drive
		/// </summary>
		/// <param name="parentFormHandle">Form used as a parent for the dialog</param>
		public void ShowConnectDialog(System.IntPtr parentFormHandle)
		{
			DoDisplayDialog(parentFormHandle, 1);
		}

		/// <summary>
		/// Display windows dialog for disconnecting a network drive (using Desktop as parent form)
		/// </summary>		
		public void ShowDisconnectDialog()
		{
			DoDisplayDialog(System.IntPtr.Zero, 2);
		}

		/// <summary>
		/// Display windows dialog for disconnecting a network drive
		/// </summary>
		/// <param name="parentFormHandle">Form used as a parent for the dialog</param>
		public void ShowDisconnectDialog(System.IntPtr parentFormHandle)
		{
			DoDisplayDialog(parentFormHandle, 2);
		}

		/// <summary>
		/// Returns the share name of a connected network drive
		/// </summary>
		/// <param name="localDrive">Drive name (eg. 'X:')</param>
		/// <returns>Share name (eg. \\computer\share)</returns>
		public string GetMappedShareName(string localDrive)
		{

			// collect and clean the passed LocalDrive param
			if (localDrive == null || localDrive.Length == 0)
				throw new System.Exception("Invalid 'localDrive' passed, 'localDrive' parameter cannot be 'empty'");
			localDrive = localDrive.Substring(0, 1);

			// call api to collect LocalDrive's share name 
			int i = 255;
			byte[] bSharename = new byte[i];
			int iCallStatus = WNetGetConnection(localDrive + ":", bSharename, ref i);
			switch (iCallStatus)
			{
				case 1201:
					throw new System.Exception("Cannot collect 'ShareName', Passed 'DriveName' is valid but currently not connected (API: ERROR_CONNECTION_UNAVAIL)");
				case 1208:
					throw new System.Exception("API function 'WNetGetConnection' failed (API: ERROR_EXTENDED_ERROR:" + iCallStatus.ToString() + ")");
				case 1203:
				case 1222:
					throw new System.Exception("Cannot collect 'ShareName', No network connection found (API: ERROR_NO_NETWORK / ERROR_NO_NET_OR_BAD_PATH)");
				case 2250:
					throw new System.Exception("Invalid 'DriveName' passed, Drive is not a network drive (API: ERROR_NOT_CONNECTED)");
				case 1200:
					throw new System.Exception("Invalid / Malfored 'Drive Name' passed to 'GetShareName' function (API: ERROR_BAD_DEVICE)");
				case 234:
					throw new System.Exception("Invalid 'Buffer' length, buffer is too small (API: ERROR_MORE_DATA)");
			}

			// return collected share name
			return System.Text.Encoding.GetEncoding(1252).GetString(bSharename, 0, i).TrimEnd((char)0);

		}

		/// <summary>
		/// Returns true if passed drive is a network drive
		/// </summary>
		/// <param name="localDrive">Drive name (eg. 'X:')</param>
		/// <returns>'True' if the passed drive is a mapped network drive</returns>
		public bool IsNetworkDrive(string localDrive)
		{

			// collect and clean the passed LocalDrive param
			if (localDrive == null || localDrive.Trim().Length == 0)
				throw new System.Exception("Invalid 'localDrive' passed, 'localDrive' parameter cannot be 'empty'");
			localDrive = localDrive.Substring(0, 1);

			// return status of drive type
			return PathIsNetworkPath(localDrive + ":");

		}

		#endregion

		#region Private functions

		// map network drive
		private void DoMapDrive(string username, string password)
		{			
			// if drive property is set to auto select, collect next free drive			
			if (_FindNextFreeDrive)
			{
				_LocalDrive = DoNextFreeDrive();
				if (_LocalDrive == null || _LocalDrive.Length == 0)
					throw new System.Exception("Could not find valid free drive name");
			}

			// create struct data to pass to the api function
			structNetResource stNetRes = new structNetResource();
			stNetRes.Scope = 2;
			stNetRes.Type = RESOURCETYPE_DISK;
			stNetRes.DisplayType = 3;
			stNetRes.Usage = 1;
			stNetRes.RemoteName = _ShareName;
			stNetRes.LocalDrive = _LocalDrive;

			// prepare flags for drive mapping options
			int iFlags = 0;
			if (_SaveCredentials)
				iFlags += CONNECT_CMD_SAVECRED;
			if (_Persistent)
				iFlags += CONNECT_UPDATE_PROFILE;
			if (_PromptForCredentials)
				iFlags += CONNECT_INTERACTIVE + CONNECT_PROMPT;

			// prepare username / password params
			if (username != null && username.Length == 0)
				username = null;
			if (password != null && password.Length == 0)
				password = null;

			// if force, unmap ready for new connection
			if (_Force)
			{
				try
				{
					this.DoUnMapDrive();
				}
				catch
				{
				}
			}

			// call and return
			int i = WNetAddConnection(ref stNetRes, password, username, iFlags);
			if (i > 0)
				throw new System.ComponentModel.Win32Exception(i);

			IsMapped = true;
		}

		// unmap network drive	
		private void DoUnMapDrive()
		{			
			// prep vars and call unmap
			int iFlags = 0;
			int iRet = 0;

			// if persistent, set flag
			if (_Persistent)
			{
				iFlags += CONNECT_UPDATE_PROFILE;
			}

			// if local drive is null, unmap with use connection
			if (_LocalDrive == null)
			{
				// unmap use connection, passing the share name, as local drive
				iRet = WNetCancelConnection(_ShareName, iFlags, System.Convert.ToInt32(_Force));
			}
			else
			{
				// unmap drive
				iRet = WNetCancelConnection(_LocalDrive, iFlags, System.Convert.ToInt32(_Force));
			}

			// if errors, throw exception
			if (iRet > 0)
				throw new System.ComponentModel.Win32Exception(iRet);

			IsMapped = false;
		}

		// check / restore a network drive
		private void DoRestoreDrive(string driveName)
		{

			// call restore and return
			int i = WNetRestoreConnection(0, driveName);

			// if error returned, throw
			if (i > 0)
				throw new System.ComponentModel.Win32Exception(i);

		}

		// display windows dialog
		private void DoDisplayDialog(System.IntPtr wndHandle, int dialogToShow)
		{

			// prep variables
			int i = -1;
			int iHandle = 0;

			// get parent handle
			if (wndHandle != System.IntPtr.Zero)
				iHandle = wndHandle.ToInt32();

			// choose dialog to show bassed on 
			if (dialogToShow == 1)
				i = WNetConnectionDialog(iHandle, RESOURCETYPE_DISK);
			else if (dialogToShow == 2)
				i = WNetDisconnectDialog(iHandle, RESOURCETYPE_DISK);

			// if error returned, throw
			if (i > 0)
				throw new System.ComponentModel.Win32Exception(i);

		}

		// returns the next viable drive name to use for mapping
		private string DoNextFreeDrive()
		{

			// loop from c to z and check that drive is free
			string retValue = null;
			for (int i = 67; i <= 90; i++)
			{
				if (GetDriveType(((char)i).ToString() + ":") == 1)
				{
					retValue = ((char)i).ToString() + ":";
					break;
				}
			}

			// return selected drive
			return retValue;

		}

		#endregion

		#region API functions / calls

		[System.Runtime.InteropServices.DllImport("mpr.dll", EntryPoint = "WNetAddConnection2A", CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
		private static extern int WNetAddConnection(ref structNetResource netResStruct, string password, string username, int flags);
		[System.Runtime.InteropServices.DllImport("mpr.dll", EntryPoint = "WNetCancelConnection2A", CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
		private static extern int WNetCancelConnection(string name, int flags, int force);
		[System.Runtime.InteropServices.DllImport("mpr.dll", EntryPoint = "WNetConnectionDialog", SetLastError = true)]
		private static extern int WNetConnectionDialog(int hWnd, int type);
		[System.Runtime.InteropServices.DllImport("mpr.dll", EntryPoint = "WNetDisconnectDialog", SetLastError = true)]
		private static extern int WNetDisconnectDialog(int hWnd, int type);
		[System.Runtime.InteropServices.DllImport("mpr.dll", CharSet = System.Runtime.InteropServices.CharSet.Unicode, SetLastError = true)]
		private static extern int WNetRestoreConnection(int hWnd, string localDrive);
		[System.Runtime.InteropServices.DllImport("mpr.dll", EntryPoint = "WNetGetConnection", SetLastError = true)]
		private static extern int WNetGetConnection(string localDrive, byte[] remoteName, ref int bufferLength);
		[System.Runtime.InteropServices.DllImport("shlwapi.dll", EntryPoint = "PathIsNetworkPath", SetLastError = true)]
		private static extern bool PathIsNetworkPath(string localDrive);
		[System.Runtime.InteropServices.DllImport("kernel32.dll", EntryPoint = "GetDriveType", SetLastError = true)]
		private static extern int GetDriveType(string localDrive);

        #region Added Functions/Functionality by JBevan
        /* *
         * After some searching online, I found some code that will look for both connected and unconnected network
         * resources, and return the drives.
         * 
         * Source: http://stackoverflow.com/questions/1898195/c-mapped-network-drives-cannot-be-listed
         * */
        [DllImport("mpr.dll", CharSet = CharSet.Auto)]
        private static extern int WNetEnumResource(IntPtr hEnum, ref int lpcCount, IntPtr lpBuffer, ref int lpBufferSize);

        [DllImport("mpr.dll", CharSet = CharSet.Auto)]
        private static extern int WNetOpenEnum(RESOURCE_SCOPE dwScope, RESOURCE_TYPE dwType, RESOURCE_USAGE dwUsage,
            [MarshalAs(UnmanagedType.AsAny)][In] object lpNetResource, out IntPtr lphEnum);

        [DllImport("mpr.dll", CharSet = CharSet.Auto)]
        private static extern int WNetCloseEnum(IntPtr hEnum);

        public enum RESOURCE_SCOPE : uint
        {
            RESOURCE_CONNECTED = 0x00000001,
            RESOURCE_GLOBALNET = 0x00000002,
            RESOURCE_REMEMBERED = 0x00000003,
            RESOURCE_RECENT = 0x00000004,
            RESOURCE_CONTEXT = 0x00000005
        }
        public enum RESOURCE_TYPE : uint
        {
            RESOURCETYPE_ANY = 0x00000000,
            RESOURCETYPE_DISK = 0x00000001,
            RESOURCETYPE_PRINT = 0x00000002,
            RESOURCETYPE_RESERVED = 0x00000008,
        }
        public enum RESOURCE_USAGE : uint
        {
            RESOURCEUSAGE_CONNECTABLE = 0x00000001,
            RESOURCEUSAGE_CONTAINER = 0x00000002,
            RESOURCEUSAGE_NOLOCALDEVICE = 0x00000004,
            RESOURCEUSAGE_SIBLING = 0x00000008,
            RESOURCEUSAGE_ATTACHED = 0x00000010,
            RESOURCEUSAGE_ALL = (RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED),
        }
        public enum RESOURCE_DISPLAYTYPE : uint
        {
            RESOURCEDISPLAYTYPE_GENERIC = 0x00000000,
            RESOURCEDISPLAYTYPE_DOMAIN = 0x00000001,
            RESOURCEDISPLAYTYPE_SERVER = 0x00000002,
            RESOURCEDISPLAYTYPE_SHARE = 0x00000003,
            RESOURCEDISPLAYTYPE_FILE = 0x00000004,
            RESOURCEDISPLAYTYPE_GROUP = 0x00000005,
            RESOURCEDISPLAYTYPE_NETWORK = 0x00000006,
            RESOURCEDISPLAYTYPE_ROOT = 0x00000007,
            RESOURCEDISPLAYTYPE_SHAREADMIN = 0x00000008,
            RESOURCEDISPLAYTYPE_DIRECTORY = 0x00000009,
            RESOURCEDISPLAYTYPE_TREE = 0x0000000A,
            RESOURCEDISPLAYTYPE_NDSCONTAINER = 0x0000000B
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct NetResource
        {
            public RESOURCE_SCOPE dwScope;
            public RESOURCE_TYPE dwType;
            public RESOURCE_DISPLAYTYPE dwDisplayType;
            public RESOURCE_USAGE dwUsage;
            [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPTStr)]
            public string lpLocalName;
            [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPTStr)]
            public string lpRemoteName;
            [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPTStr)]
            public string lpComment;
            [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPTStr)]
            public string lpProvider;
        }

        static System.Collections.Generic.Dictionary<string, NetResource> WNetResource(object resource)
        {
            System.Collections.Generic.Dictionary<string, NetResource> result = new System.Collections.Generic.Dictionary<string, NetResource>();

            int iRet;
            IntPtr ptrHandle = new IntPtr();
            try
            {
                iRet = WNetOpenEnum(
                    RESOURCE_SCOPE.RESOURCE_REMEMBERED, RESOURCE_TYPE.RESOURCETYPE_DISK, RESOURCE_USAGE.RESOURCEUSAGE_ALL,
                    resource, out ptrHandle);
                if (iRet != 0)
                    return null;

                int entries = -1;
                int buffer = 16384;
                IntPtr ptrBuffer = Marshal.AllocHGlobal(buffer);
                NetResource nr;

                iRet = WNetEnumResource(ptrHandle, ref entries, ptrBuffer, ref buffer);
                while ((iRet == 0) || (entries > 0))
                {
                    Int32 ptr = ptrBuffer.ToInt32();
                    for (int i = 0; i < entries; i++)
                    {
                        nr = (NetResource)Marshal.PtrToStructure(new IntPtr(ptr), typeof(NetResource));
                        if (RESOURCE_USAGE.RESOURCEUSAGE_CONTAINER == (nr.dwUsage
                            & RESOURCE_USAGE.RESOURCEUSAGE_CONTAINER))
                        {
                            //call recursively to get all entries in a container
                            WNetResource(nr);
                        }
                        ptr += Marshal.SizeOf(nr);
                        nr.lpLocalName += "\\";
                        result.Add(nr.lpLocalName, nr);
                    }

                    entries = -1;
                    buffer = 16384;
                    iRet = WNetEnumResource(ptrHandle, ref entries, ptrBuffer, ref buffer);
                }

                Marshal.FreeHGlobal(ptrBuffer);
                iRet = WNetCloseEnum(ptrHandle);
            }
            catch (Exception)
            {
            }

            return result;
        }
        public static System.Collections.Generic.Dictionary<string, NetResource> WNetResource()
        {
            return WNetResource(null);
        }
        #endregion

        [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
		private struct structNetResource
		{
			public int Scope;
			public int Type;
			public int DisplayType;
			public int Usage;
			public string LocalDrive;
			public string RemoteName;
			public string Comment;
			public string Provider;
		}

		// standard
		private const int RESOURCETYPE_DISK = 0x1;
		private const int CONNECT_INTERACTIVE = 0x00000008;
		private const int CONNECT_PROMPT = 0x00000010;
		private const int CONNECT_UPDATE_PROFILE = 0x00000001;

		// ie4+
		private const int CONNECT_REDIRECT = 0x00000080;

		// nt5+
		private const int CONNECT_COMMANDLINE = 0x00000800;
		private const int CONNECT_CMD_SAVECRED = 0x00001000;

		#endregion

	}


}